import java.awt.Color;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JLabel;


/**
 * This class represents the Game Model, which according to our design, will hold all of the variables 
 * needed for the MULE game to run.
 * @author Srinidhi Chaganty, Megan Beebe, Sherman Mathews, Sammy Warrayat
 *
 */
public class GameModel implements Serializable {
	// instance is a GameModel object
	private static GameModel instance = new GameModel();
	public static GameModel getInstance(){ return instance;}

	//This class contains all of the different variables and their respective getters/setters
	/*
	 * difficulty is the difficulty of the Game
	 * typeMap is the map type (standard/random) of the Game
	 * numPlayers is the number of Players playing the Game
	 * playerArr is an array that has a length of numPlayers and holds all of the PlayerGUIs
	 * map is a map object
	 * players is an arrayList of all of the players (length is numPlayers)
	 * turnTimer is a turnTimer object
	 * numRounds is the number of Rounds in the Game
	 * comp is a comparator object used to see the player order (depending on score) for each Round
	 * round is a Round object
	 * t is a Turn object
	 * store is a Store object
	 * label is a JLabel that is used to describe what screen the Game is on
	 * sPan is a StorePanel object
	 * currPlayer is the current Player playing the Game (used for saving the Game)
	 * roundsLeft is a int value that tells us how many rounds are left in the Game
	 */
	private String difficulty; //saved
	private String typeMap; //saved
	private int numPlayers; //saved
	private PlayerGUI[] playerArr; //saved
	private Map map;
	private ArrayList<Player> players; //saved
	private TurnTimer turnTimer; //saved
	private int numRounds; //saved
	private ComparePlayer comp = new ComparePlayer();
	private Round round; //saved
	private transient Turn t;
	private transient Store store;
	private transient JLabel label = new JLabel();
	private transient StorePanel sPan;
	private Player currPlayer; //save
	private int roundsLeft;

	//colors
	/*
	 * ourBlue represents the color blue used in our Game
	 * ourRed represents the color red used in our Game
	 * ourYellow represents the color yellow used in our Game
	 * ourPurple represents the color purple used in our Game
	 * plainGreen represents the color green (for plains) used in our Game
	 * townOrange represents the color orange (for Town) used in our Game
	 * mountainBrown represents the color brown (for mountains) used in our Game
	 * riverBlue represents the color blue (for rivers) used in our Game
	 */
	public static final transient Color ourBlue = new Color(77, 199, 253);
	public static final transient Color ourRed = new Color(205, 30, 16);
	public static final transient Color ourYellow = new Color(250, 223, 0);
	public static final transient Color ourPurple = new Color(126, 119, 210);
	public static final transient Color plainGreen = new Color(0, 126, 58);
	public static final transient Color townOrange = new Color(241, 171, 0);
	public static final transient Color mountainBrown = new Color(118, 57, 49);
	public static final transient Color riverBlue = new Color(0, 38, 133);

	//saving stuff
	/*
	 * serialVersionUID is used for saving (using serialization) the Game
	 * myLogger is used for loading (using serialization) the Game
	 */
	private static final long serialVersionUID = 1L;
	private static Logger myLogger = Logger.getLogger("game");

	/**
	 * the no-args constructor for GameModel
	 */
	public GameModel(){
		comp = new ComparePlayer();
		store = new Store();
	}
	
	public void generateMap(boolean random) {
		map = new Map(new Town());
		if (random){
			map.setMap(map.randomMap());
		}
		else {
			map.setMap(map.standardMap());
		}
		
	}

	/**
	 * Sets the difficulty of the game to the parameter diff
	 * @param diff is the string that represents the difficulty
	 */
	public void setDifficulty(String diff) {
		difficulty = diff;
	}

	/**
	 * Sets the label of the Model to the text passed in
	 * @param text the text to set the label to
	 */
	public void setLabel(String text) {
		label.setText(text);
	}

	/**
	 * returns the number of rounds
	 * @return int the number of rounds
	 */
	public int getNumRounds() {
		if ("EASY".equalsIgnoreCase(difficulty)) {
			numRounds = 10;
		}
		else
			numRounds = 12;
		return numRounds;
	}


	/** 
	 * Gets the difficulty
	 * @return difficulty
	 */
	public String getDifficulty(){
		return difficulty;
	}

	/**
	 * Sets the map type 
	 * @param newType is the type of map
	 */
	public void setTypeMap(String newType){
		typeMap = newType;
	}

	/**
	 * Gets the map type
	 * @return typeMap
	 */
	public String getTypeMap(){
		return typeMap;
	}

	/**
	 * Gets the map
	 * @return map
	 */
	public Map getMap(){
		return map;
	}

	/**
	 * sorts Players using the java.util.Collections sort method and a private inner comparator
	 *  
	 * @param players the ArrayList to be sorted
	 */
	@SuppressWarnings("unchecked")
	public void sortPlayers(ArrayList<Player> players){
		java.util.Collections.sort(players, this.comp);
	}

	/**
	 * Gets the arrayList of Players
	 * @return players
	 */
	public ArrayList<Player> getPlayers() {
		return players;
	}

	/**
	 * Sets the arrayList of Players
	 * @param play is the arraylist of Player objects
	 */
	public void setPlayers(ArrayList<Player> play){
		players = play;
	}

	/**
	 * sets the amount of players playing the game
	 * @param numPlayers is the number of players
	 */
	public void setNumPlayers(int numPlayers) {
		this.numPlayers = numPlayers;
	}


	/**
	 * Gets the PlayerGUI array
	 * @return playerArr
	 */
	public PlayerGUI[] getPlayerArr() {
		return playerArr;
	}

	/**
	 * Gets the number of players
	 * @return numPlayers
	 */
	public int getNumPlayers() {
		return numPlayers;
	}

	/**
	 * gets the store
	 * @return the store
	 */

	public Store getStore(){
		return store;
	}

	/**
	 * Sets the panel for the store
	 * @param s
	 */

	public void setStorePanel(StorePanel s){
		sPan = s;
	}

	/**
	 * Returns the store panel
	 * @return sPan
	 */

	public StorePanel getStorePanel(){
		return sPan;
	}

	/**
	 * Sets the playerGUI array to the parameter
	 * @param pG is a PlayerGUI array
	 */
	public void setPlayerArr(PlayerGUI[] pG) {
		playerArr = pG;
	}

	/**
	 * Gets the turn timer
	 * @return turnTimer
	 */

	public TurnTimer getTurnTimer() {
		return turnTimer;
	}

	/**
	 * Sets the turn timer
	 * @param turnTimer
	 */

	public void setTurnTimer(TurnTimer turnTimer) {
		this.turnTimer = turnTimer;
	}

	/**
	 * Sets the round
	 * @param round2
	 */

	public void setRound(Round round) {
		this.round = round;
	}

	/**
	 * Returns the current round
	 * @return round
	 */

	public Round getRound() {
		return round;
	}

	/**
	 * Returns the player in last place
	 * @return Player the player in last place
	 */
	public Player getLastPlace(){
		//if players are tied for last then last in original turn order is considered last place
		//lastPlace is a Player temp object that will go last in the next Round
		Player lastPlace = players.get(0);
		//lowestMoney is an int which holds the amount of money of lastPlace player
		int lowestMoney = players.get(0).getMoney();
		for (Player player : players){
			if (player.getMoney() <= lowestMoney){
				lastPlace = player;
				lowestMoney = player.getMoney();
			}
		}
		return lastPlace;
	}

	/**
	 * Gets the current player
	 * @return currPlayer
	 */

	public Player getCurrPlayer() {
		return currPlayer;
	}

	/**
	 * Sets the current player
	 * @param currPlayer
	 */

	public void setCurrPlayer(Player currPlayer) {
		this.currPlayer = currPlayer;
	}

	/**
	 * Private inner class that compares Players by looking at their Money
	 * 
	 * @author Srinidhi Chaganty
	 *
	 */

	@SuppressWarnings("rawtypes")
	private class ComparePlayer implements Comparator, Serializable{

		public ComparePlayer() {

		}

		/**
		 * An override of the compare method in the Comparator interface
		 * returns 1 if o1 > o2, 0 if o1 = o2 and -1 if o1 < o2
		 * 
		 * @param o1 the first Object
		 * @param o2 the second Object
		 * @return int the number corresponding to the comparison
		 */
		@Override
		public int compare(Object o1, Object o2) {
			if(((Player)o1).getMoney() > ((Player)o2).getMoney()){
				return 1;
			}
			else if(((Player)o1).getMoney() == ((Player)o2).getMoney()){
				return 0;
			}
			return -1;
		}

	}


	/**
	 * Save this object via serialization
	 * 
	 * @param filename the name of the file to save to
	 */
	public void save(String filename) {
		try {
			/*
			 * Create the object output stream for serialization.
			 * We are wrapping a FileOutputStream since we
			 * want to save to disk.  You can also save to socket
			 * streams or any other kind of stream.
			 */
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename));

			/*
			 * The only real call we need.  The stream buffers the output and reuses
			 * data, so if you are serializing very frequently, then the object values might
			 * not actually change because the old serialized object is being reused.
			 * 
			 * To fix this you can try writeUnshared() or you can reset the stream.
			 * out.reset();
			 */

			out.writeObject(this);

		} catch (FileNotFoundException e) {
			myLogger.log(Level.SEVERE, "Save file not found: " + filename, e);
		} catch (IOException e) {
			myLogger.log(Level.SEVERE, "General IO Error on saving: " + filename, e);
		}

	}



	/**
	 * This is an example of a factory method
	 * We call this static method to create an
	 * instance of Company from a serialized file.
	 * 
	 * @param filename the name of the file to use
	 * @return an instance of Company initialized from a file.
	 */
	public static GameModel getFromFile(String filename) {
		GameModel c = null;
		try {
			/*
			 * Create the input stream.  Since we want to read from the disk, 
			 * we wrap a file stream.
			 */
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
			/*
			 * Now we can read the entire company in with only one call
			 */
			c = (GameModel) in.readObject();

		} catch (FileNotFoundException e) {
			myLogger.log(Level.SEVERE, "Load file not found: " + filename, e);
		} catch (IOException e) {
			myLogger.log(Level.SEVERE, "General IO Error on loading: " + filename, e);
		} catch (ClassNotFoundException e) {
			myLogger.log(Level.SEVERE, "Class not found on loading: " + filename, e);
		}
		return c;
	}

	/*
	 * sets the number of Rounds (numRounds)
	 * @param num is the number of Rounds
	 */
	public void setNumRounds(int num) {
		numRounds = num;

	}

	/*
	 * sets the turn 
	 * @param turn is the turn
	 */
	public void setTurn(Turn turn) {
		t = turn;
	}

	/*
	 * gets the current Turn
	 * @return t is the instance variable turn 
	 */
	public Turn getTurn() {
		return t;
	}

	/*
	 * gets the label
	 * @return label is the instance varible label
	 */
	public JLabel getLabel() {
		return label;
	}

	/*
	 * sets the label 
	 * @param label is the label
	 */
	public void setLabel(JLabel label) {
		this.label = label;
	}

	/*
	 * sets the Map
	 * @param map is the Map
	 */
	public void setMap(Map map) {
		this.map = map;

	}

	/*
	 * sets the number of Rounds left (roundsLeft)
	 * @param i is the number of Rounds left
	 */
	public void setRoundsLeft(int i) {
		roundsLeft = i;

	}

	/*
	 * gets the number of Rounds left (roundsLeft)
	 * @return roundsLeft is the instance variable representing the rounds that are remaining
	 */
	public int getRoundsLeft() {
		return roundsLeft;
	}


}